/*
 * ComplexType.java
 *
 * Created on 20 marzo 2004, 1.08
 */

package net.ermannofranco.xml.schema;

import java.util.ArrayList;

import net.ermannofranco.xml.BlankLine;
import net.ermannofranco.xml.ITag;
import net.ermannofranco.xml.Indent;
import net.ermannofranco.xml.Static;

/**
 * 
 * @author ermanno (ermanno.franco@gmail.com)
 */
public class ComplexType extends SchemaTag implements ContentType {

	private static final long serialVersionUID = -1267386254651819601L;

	private final java.util.List<ITag> attributes = new ArrayList<ITag>();
	// private static final int EXTENSION=-1;

	// private boolean mixed;
	private Content gestoreContenuto;
	private boolean built = false;

	private SimpleContent simpleContent;

	private AnyAttribute anyAttribute;

	private boolean attributeAssigned;

	/**
	 * Creates a new instance of ComplexType Protetto
	 * 
	 * @param level
	 *            livello di indentazione
	 * @param container
	 *            tag contenitore
	 */
	protected ComplexType(int level, ISchemaTag container) {
		super("complexType", level, container);
	}

	/**
	 * @param flag
	 * @return
	 */
	public ComplexType setAbstract(boolean flag) {
		addAttr("abstract", "" + flag);
		return this;
	}

	/**
	 * @param mixed
	 * @return
	 */
	public ComplexType setMixed() {
		// if (gestoreContenuto==SIMPLECONTENT) throw new
		// SchemaException("Impossibile assegnare mixed ad un ComplexType con simpleContent");
		addAttr("mixed", "true");
		return this;
	}

	/**
	 * @param name
	 * @return
	 */
	public ComplexType setName(String name) {
		Static.validateNMToken(name);

		addAttr("name", name);
		return this;
	}

	/*
	 * public Extension setExtension(int baseType) { if (gestoreContenuto>0)
	 * throw new SchemaException("Contenuto di ComplexType gia' assegnato");
	 * Extension ex; add(ex=new Extension(getLevel()+1,
	 * this).setBase(baseType)); gestoreContenuto=EXTENSION; return ex; }
	 */

	// public Attribute addAttributeTag() {
	// // if (gestoreContenuto == 0)
	// // throw new SchemaException(
	// // "Assegnare contenuto prima di addAttributeTag");
	// Attribute att;
	// add(att = new Attribute(getLevel() + 1, this));
	// return att;
	// }

	/**
	 * add a "Attribute" tag with given name.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name) {
		if (gestoreContenuto == Content.COMPLEXCONTENT
				|| gestoreContenuto == Content.SIMPLECONTENT)
			throw new SchemaException(
					"Attribute Tag not allowed, simple or complex content assigned");
		Attribute att;
		attributes.add(att = new Attribute(getLevel() + 1, this));
		att.setName(name);
		attributeAssigned = true;
		return att;
	}

	/**
	 * add a "Attribute" tag with given name and use.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, Use use) {
		Attribute ret = addTagAttribute(name);
		ret.setUse(use);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given reference.
	 * 
	 * @return Attribute
	 */
	public ComplexType addTagAttribute(Attribute ref) {
		if (gestoreContenuto == Content.COMPLEXCONTENT
				|| gestoreContenuto == Content.SIMPLECONTENT)
			throw new SchemaException(
					"Attribute Tag not allowed, simple or complex content assigned");
		Attribute att;
		attributes.add(att = new Attribute(getLevel() + 1, this));
		att.setRef(ref);
		attributeAssigned = true;
		return this;
	}

	/**
	 * add a "Attribute" tag with given reference and use.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(Attribute ref, Use use) {
		if (gestoreContenuto == Content.COMPLEXCONTENT
				|| gestoreContenuto == Content.SIMPLECONTENT)
			throw new SchemaException(
					"Attribute Tag not allowed, simple or complex content assigned");
		Attribute att;
		attributes.add(att = new Attribute(getLevel() + 1, this));
		att.setRef(ref).setUse(use);
		attributeAssigned = true;
		return att;
	}

	/**
	 * add a "Attribute" tag with given name and type.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, XmlType type) {
		return addTagAttribute(name).setType(type);
	}

	/**
	 * add a "Attribute" tag with given name, type and use.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, XmlType type, Use use) {
		return addTagAttribute(name).setType(type).setUse(use);
	}

	/**
	 * add a "Attribute" tag with given name and type reference.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, ContentType typeRef) {
		Attribute ret = addTagAttribute(name);
		ret.setType(typeRef);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given name, type reference and use.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, ContentType typeRef, Use use) {
		Attribute ret = addTagAttribute(name, typeRef);
		ret.setUse(use);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given name and type name.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, String typeName) {
		Attribute ret = addTagAttribute(name);
		ret.setType(typeName);
		return ret;
	}

	/**
	 * add a "Attribute" tag with given name, type name and use.
	 * 
	 * @return Attribute
	 */
	public Attribute addTagAttribute(String name, String typeName, Use use) {
		Attribute ret = addTagAttribute(name, typeName);
		ret.setUse(use);
		return ret;
	}

	/**
	 * Add an already declared first level AttributeGroup reference.
	 * 
	 * @return
	 */
	public AttributeGroup addAttributeGroup(AttributeGroup ref) {
		if (gestoreContenuto == Content.COMPLEXCONTENT
				|| gestoreContenuto == Content.SIMPLECONTENT)
			throw new SchemaException(
					"Attribute Tag not allowed, content assigned");
		AttributeGroup att;
		attributes.add(att = new AttributeGroup(getLevel() + 1, this));
		att.setRef(ref);
		attributeAssigned = true;
		return att;
	}

	/**
	 * @return
	 */
	public Sequence setSequence() {
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		Sequence s = new Sequence(getLevel() + 1, this, true);
		add(s);
		gestoreContenuto = Content.SEQUENCE;
		return s;
	}

	/**
	 * @return
	 */
	public Choice setChoice() {
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		Choice s = new Choice(getLevel() + 1, this);
		add(s);
		gestoreContenuto = Content.CHOICE;
		return s;
	}

	/**
	 * @return
	 */
	public Group setGroup(Group groupRef) {
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		Group s = new Group(getLevel() + 1, this);
		s.setRef(groupRef);
		add(s);
		gestoreContenuto = Content.GROUP;
		return s;
	}

	/**
	 * @return
	 */
	public All setAll() {
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		All s = new All(getLevel() + 1, this);
		add(s);
		gestoreContenuto = Content.ALL;
		return s;
	}

	public AnyAttribute setAnyAttribute() {
		if (anyAttribute != null)
			throw new SchemaException("AnyAttribute gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		anyAttribute = new AnyAttribute(getLevel() + 1, this);
		return anyAttribute;
	}

	public AnyAttribute setAnyAttribute(String... namespace) {
		return setAnyAttribute().setNamespace(namespace);
	}

	public AnyAttribute setAnyAttribute(NsConstants namespace,
			Process processContent) {
		return setAnyAttribute().setNamespace(namespace.getVal())
				.processContents(processContent);
	}

	public AnyAttribute setAnyAttribute(Process processContent) {
		return setAnyAttribute().processContents(processContent);
	}

	/**
	 * @return
	 */
	public SimpleContent setSimpleContent() {
		if (attributeAssigned)
			throw new SchemaException(
					"Attributes and SimpleContent not allowed together");
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		// if (mixed) throw new
		// SchemaException("Impossibile assegnare simpleContent ad un ComplexType mixed");
		SimpleContent s = new SimpleContent(getLevel() + 1, this);
		add(s);
		gestoreContenuto = Content.SIMPLECONTENT;
		simpleContent = s;
		return s;
	}

	/**
	 * @return
	 */
	public ComplexContent setComplexContent() {
		if (attributeAssigned)
			throw new SchemaException(
					"Attributes and ComplexContent not allowed together");
		if (gestoreContenuto != null)
			throw new SchemaException("Contenuto di ComplexType gia' assegnato");
		ComplexContent s = new ComplexContent(getLevel() + 1, this);
		add(s);
		gestoreContenuto = Content.COMPLEXCONTENT;
		return s;
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation
	 * 
	 * @param explanation
	 *            Stringa di documentazione
	 * @return autoriferimento all'oggetto corrente
	 */
	public Documentation addDocumentation(String explanation) {
		return addAnnotation().addDocumentation(explanation);
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation.
	 * 
	 * @param explanation
	 *            testo della documentazione
	 * @param language
	 *            attributo di xml:lang, seguire le regole di i18n
	 * @return riferimento all'oggetto corrente
	 */
	public Documentation addDocumentation(String explanation, String language) {
		return addAnnotation().addDocumentation(explanation, language);
	}

	@Override
	public synchronized String toPrint() {
		if (!built) {
			build();
		}
		return super.toPrint();
	}

	@Override
	public synchronized String toSave() {
		if (!built) {
			build();
		}
		return super.toSave();
	}

	@Override
	public synchronized void setPrintIndent(Indent indent) {
		if (!built) {
			build();
		}
		super.setPrintIndent(indent);
	}

	@Override
	public synchronized void setSaveIndent(Indent indent) {
		if (!built) {
			build();
		}
		super.setSaveIndent(indent);
	}

	@Override
	protected synchronized final void setBlankLine(BlankLine blankLine) {
		if (!built) {
			build();
		}
		super.setBlankLine(blankLine);
	}

	private synchronized void build() {
		for (int i = 0; i < attributes.size(); i++) {
			add(attributes.get(i));
		}
		if (anyAttribute != null) {
			add(anyAttribute);
		}
		built = true;
	}

	/**
	 * Fornisce il nome del tipo, per uso in setRef():
	 */
	public String getTypeName() {
		String ret = getAttribute("name");
		if (ret == null)
			return "<complexType_without_name";
		return ret;
	}

	public boolean isNotVeryComplex() {
		return simpleContent != null;
	}

	public java.util.List<XmlType> getBaseTypes() {
		if (isNotVeryComplex()) {
			return simpleContent.getBaseTypes();
		} else
			return null;
	}

}
